package cn.aws360.boot.system.biz.service.impl;

import cn.aws360.boot.base.api.constant.enums.DelFlagEnum;
import cn.aws360.boot.base.api.exception.ServiceException;
import cn.aws360.boot.data.api.constant.DataConstant;
import cn.aws360.boot.data.api.factory.PageFactory;
import cn.aws360.boot.data.api.pojo.request.PageQuery;
import cn.aws360.boot.data.api.pojo.response.PageResult;
import cn.aws360.boot.data.api.utils.PojoUtils;
import cn.aws360.boot.system.api.constant.SysConstant;
import cn.aws360.boot.system.api.exception.SysExceptionEnum;
import cn.aws360.boot.system.api.pojo.request.SysRoleDataWrite;
import cn.aws360.boot.system.api.pojo.request.SysRoleMenuWrite;
import cn.aws360.boot.system.api.pojo.request.SysRoleQuery;
import cn.aws360.boot.system.api.pojo.request.SysRoleWrite;
import cn.aws360.boot.system.api.pojo.response.SysRoleModel;
import cn.aws360.boot.system.biz.domain.SysRole;
import cn.aws360.boot.system.biz.domain.SysRoleMenu;
import cn.aws360.boot.system.biz.domain.SysUserRole;
import cn.aws360.boot.system.biz.mapper.SysRoleMapper;
import cn.aws360.boot.system.biz.service.SysRoleMenuService;
import cn.aws360.boot.system.biz.service.SysRoleService;
import cn.aws360.boot.system.biz.service.SysUserRoleService;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author junnan
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements SysRoleService {

    private final SysUserRoleService userRoleService;
    private final SysRoleMenuService roleMenuService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_ROLE_CACHE, allEntries = true)
    public Boolean add(SysRoleWrite sysRoleWrite) {
        SysRole sysRole = BeanUtil.copyProperties(sysRoleWrite, SysRole.class, DataConstant.ID);
        checkCodeHasExist(sysRole);
        return this.save(sysRole);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_ROLE_CACHE, allEntries = true)
    public Boolean edit(SysRoleWrite sysRoleWrite) {
        Integer roleId = sysRoleWrite.getId();
        checkSysDontUpdate(roleId);
        SysRole sysRole = this.getSysRoleById(roleId);
        BeanUtil.copyProperties(sysRoleWrite, sysRole);
        checkCodeHasExist(sysRole);
        return this.updateById(sysRole);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_ROLE_CACHE, allEntries = true)
    public Boolean assignMenu(SysRoleMenuWrite sysRoleMenuWrite) {
        Integer roleId = sysRoleMenuWrite.getRoleId();
        checkSysDontUpdate(roleId);
        LambdaQueryWrapper<SysRoleMenu> queryWrapper = Wrappers.<SysRoleMenu>lambdaQuery().eq(SysRoleMenu::getRoleId, roleId);
        roleMenuService.getBaseMapper().delete(queryWrapper);

        String menuIds = sysRoleMenuWrite.getMenuIds();
        if (StrUtil.isNotBlank(menuIds)) {
            List<Integer> menuIdList = Convert.toList(Integer.class, menuIds.split(StrUtil.COMMA));
            List<SysRoleMenu> roleMenuList = new HashSet<>(menuIdList).stream().map(menuId ->
                    SysRoleMenu.builder().roleId(roleId).menuId(menuId).build()).collect(Collectors.toList());
            return roleMenuService.saveBatch(roleMenuList);
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_ROLE_CACHE, allEntries = true)
    public Boolean assignData(SysRoleDataWrite sysRoleDataWrite) {
        Integer roleId = sysRoleDataWrite.getRoleId();
        checkSysDontUpdate(roleId);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = SysConstant.MODULE_ROLE_CACHE, allEntries = true)
    public Boolean remove(String ids) {
        List<Integer> roleIdList = Convert.toList(Integer.class, ids.split(StrUtil.COMMA));
        roleIdList.forEach(this::checkSysDontDelete);
        this.listByIds(roleIdList).forEach(this::checkHasUserDontDelete);
        LambdaQueryWrapper<SysRoleMenu> queryWrapper = Wrappers.<SysRoleMenu>lambdaQuery().in(SysRoleMenu::getRoleId, roleIdList);
        roleMenuService.getBaseMapper().delete(queryWrapper);
        return this.removeByIds(roleIdList);
    }

    @Override
    public SysRoleModel detail(Integer id) {
        SysRole sysRole = this.getSysRoleById(id);
        SysRoleModel sysRoleModel = BeanUtil.copyProperties(sysRole, SysRoleModel.class);
        return expandSysRoleModel(sysRoleModel);
    }

    @Override
    public List<SysRoleModel> findList(SysRoleQuery sysRoleQuery) {
        LambdaQueryWrapper<SysRole> queryWrapper = createQueryWrapper(sysRoleQuery);
        List<SysRole> sysRoleList = this.list(queryWrapper);
        List<SysRoleModel> sysRoleModelList = PojoUtils.copyToList(sysRoleList, SysRoleModel.class);
        return expandSysRoleModel(sysRoleModelList);
    }

    @Override
    public PageResult<SysRoleModel> findPage(SysRoleQuery sysRoleQuery, PageQuery pageQuery) {
        LambdaQueryWrapper<SysRole> queryWrapper = createQueryWrapper(sysRoleQuery);
        PageResult<SysRole> sysRolePage = PageFactory.getPageResult(this, pageQuery, queryWrapper);
        PageResult<SysRoleModel> sysRoleModelPage = PojoUtils.copyToPage(sysRolePage, SysRoleModel.class);
        sysRoleModelPage.setRows(expandSysRoleModel(sysRoleModelPage.getRows()));
        return sysRoleModelPage;
    }

    private SysRole getSysRoleById(Integer id) {
        SysRole sysRole = this.getById(id);
        checkNotExist(sysRole);
        return sysRole;
    }

    private LambdaQueryWrapper<SysRole> createQueryWrapper(SysRoleQuery roleQuery) {
        return Wrappers.<SysRole>lambdaQuery()
                .like(ObjectUtil.isNotEmpty(roleQuery.getName()), SysRole::getName, roleQuery.getName())
                .like(ObjectUtil.isNotEmpty(roleQuery.getCode()), SysRole::getCode, roleQuery.getCode())
                .orderByAsc(SysRole::getId);
    }

    private SysRoleModel expandSysRoleModel(SysRoleModel sysRoleModel) {
        Integer roleId = sysRoleModel.getId();
        LambdaQueryWrapper<SysRoleMenu> queryWrapper = Wrappers.<SysRoleMenu>lambdaQuery().eq(SysRoleMenu::getRoleId, roleId);
        List<SysRoleMenu> sysRoleMenuList = roleMenuService.list(queryWrapper);
        Set<Integer> menuIdSet = sysRoleMenuList.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet());
        sysRoleModel.setMenuIds(StrUtil.join(StrUtil.COMMA, menuIdSet));
        return sysRoleModel;
    }

    private List<SysRoleModel> expandSysRoleModel(List<SysRoleModel> sysRoleModelList) {
        Map<Integer, List<SysRoleMenu>> groupMap = new HashMap<>();
        Set<Integer> roleIdSet = sysRoleModelList.stream().map(SysRoleModel::getId).collect(Collectors.toSet());
        if (CollUtil.isNotEmpty(roleIdSet)) {
            LambdaQueryWrapper<SysRoleMenu> queryWrapper = Wrappers.<SysRoleMenu>lambdaQuery().in(SysRoleMenu::getRoleId, roleIdSet);
            List<SysRoleMenu> sysRoleMenuList = roleMenuService.list(queryWrapper);
            groupMap = sysRoleMenuList.stream().collect(Collectors.groupingBy(SysRoleMenu::getRoleId));
        }

        for (SysRoleModel sysRoleModel : sysRoleModelList) {
            Integer roleId = sysRoleModel.getId();
            List<SysRoleMenu> roleMenuList = groupMap.get(roleId);
            if (CollUtil.isNotEmpty(roleMenuList)) {
                Set<Integer> menuIdSet = roleMenuList.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toSet());
                sysRoleModel.setMenuIds(StrUtil.join(StrUtil.COMMA, menuIdSet));
            }
        }
        return sysRoleModelList;
    }

    private void checkNotExist(SysRole sysRole) {
        if (ObjectUtil.isNull(sysRole) || DelFlagEnum.Y.getValue().equals(sysRole.getDelFlag())) {
            throw new ServiceException(SysExceptionEnum.ROLE_NOT_EXIST);
        }
    }

    private void checkCodeHasExist(SysRole sysRole) {
        Integer id = sysRole.getId();
        String code = sysRole.getCode();
        id = (ObjectUtil.isNotNull(id) && id != 0) ? id : -1;
        SysRole existRole = this.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getCode, code));
        if (ObjectUtil.isNotNull(existRole) && !existRole.getId().equals(id)) {
            throw new ServiceException(SysExceptionEnum.ROLE_CODE_HAS_EXIST, code);
        }
    }

    private void checkSysDontUpdate(Integer roleId) {
        if (SysConstant.ROOT_ID.equals(roleId)) {
            throw new ServiceException(SysExceptionEnum.ROLE_DONT_UPDATE_FOR_ROOT);
        }
    }

    private void checkSysDontDelete(Integer roleId) {
        if (SysConstant.ROOT_ID.equals(roleId)) {
            throw new ServiceException(SysExceptionEnum.ROLE_DONT_DELETE_FOR_ROOT);
        }
    }

    private void checkHasUserDontDelete(SysRole sysRole) {
        Integer roleId = sysRole.getId();
        LambdaQueryWrapper<SysUserRole> queryWrapper = Wrappers.<SysUserRole>lambdaQuery().eq(SysUserRole::getRoleId, roleId);
        List<SysUserRole> sysUserRoleList = userRoleService.list(queryWrapper);
        if (CollUtil.isNotEmpty(sysUserRoleList)) {
            throw new ServiceException(SysExceptionEnum.ROLE_DONT_DELETE_FOR_HAS_USER, roleId);
        }
    }

}
